#ifndef IMAGEHELPER_IMPL_H
#define IMAGEHELPER_IMPL_H

/*! \author Guillaume Collombet */

#include "opencv2/opencv.hpp"

#include <functional>

using namespace cv;

template<typename T1, typename T2, int d1, int d2>
void processImage(const Mat &input, Mat &output, std::function<T2 (const Vec<T1, d1> &, int)> func)
{
    static_assert((d1 > 0) && (d2 > 0),"d1 and d2 must be greaters than zero.");
    assert((input.dims == d1) && (output.dims == d2));
    assert((input.cols == output.cols) && (input.rows == output.rows));
    assert((input.channels() == d1) && (output.channels() == d2));

    //Create output if necessary
    if(!((output.type() == DataType<Vec<T2,d2>>::type)
         && (output.cols == input.cols)
         && (output.rows == input.rows)))
        output.create(input.rows,input.cols,DataType<Vec<T2,d2>>::type);

    for(int y = 0; y < input.rows; y++)
        for(int x = 0; x < input.cols; x++)
            for(int i = 0; i < d2; i++)
                output.at<Vec<T2,d2>>(y,x)[i] = func(input.at<T1>(y,x),i);
}

template<typename T, int d, typename Func>
void processImage(const Mat &input, Mat &output, Func func) //std::function<T (const Vec<T, d> &, int)>
{
    typedef Vec<T,d> _Vec;
    static_assert(d > 0,"d must be greater than zero.");
    assert(input.channels() == d);
    assert((input.cols == output.cols)
           && (input.rows == output.rows)
           && (input.type() == output.type()));

    //Create output if necessary
    if(!((output.type() == input.type())
         && (output.cols == input.cols)
         && (output.rows == input.rows)))
        output.create(input.rows,input.cols,input.type());

    for(int y = 0; y < input.rows; y++) {
        for(int x = 0; x < input.cols; x++) {
            const _Vec &in_vec = input.at<_Vec>(y,x);
            _Vec &out_vec = output.at<_Vec>(y,x);
            for(int i = 0; i < d; i++)
                func(in_vec[i],out_vec[i]);
        }
    }
}

template<typename T, int d, typename Func>
void processImage(const Mat &input, Func func) //std::function<void (T)>
{
    typedef Vec<T,d> _Vec;
    static_assert(d > 0,"d must be greater than zero.");
    assert(input.channels() == d);
    for(int y = 0; y < input.rows; y++) {
        for(int x = 0; x < input.cols; x++) {
            const _Vec &in_vec = input.at<_Vec>(y,x);
            for(int i = 0; i < d; i++)
                func(in_vec[i]);
        }
    }
}

template<typename T, int d> T max(const Mat &input)
{
    T _max;
    processImage<T,d>(input,[&_max] (const T& val) { if (_max < val) _max = val; } );
    return _max;
}

template<typename T, int d> T min(const Mat &input)
{
    T _min;
    processImage<T,d>(input,[&_min] (const T& val) { if (_min >= val) _min = val; } );
    return _min;
}

//CV_8U - 8-bit unsigned integers ( 0..255 )
//CV_8S - 8-bit signed integers ( -128..127 )
//CV_16U - 16-bit unsigned integers ( 0..65535 )
//CV_16S - 16-bit signed integers ( -32768..32767 )
//CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
//CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
//CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
template <typename T>
std::pair<T,T> minmax(const Mat &input)
{
    switch (input.depth()) {
    case CV_8U:
        return std::pair<T,T>(0,255);
        break;
    case CV_8S:
        return std::pair<T,T>(-128,127);
        break;
    case CV_16U:
        return std::pair<T,T>(0,65535);
        break;
    case CV_16S:
        return std::pair<T,T>(-32768,32767);
        break;
    case CV_32S:
        return std::pair<T,T>(-2147483648,2147483647);
        break;
    case CV_32F:
        return std::pair<T,T>(-FLT_MAX,FLT_MAX);
        break;
    case CV_64F:
        return std::pair<T,T>(-DBL_MAX,DBL_MAX);
        break;
    }
}

template<typename T, int d>
void extendDynamicly(const Mat &input, Mat &output)
{
    T _min = min<T,d>(input);
    T _max = max<T,d>(input);
    T a = minmax<T>(input).second / (_max - _min);
    T b = -a * _min;
    processImage<T,d>(input,output,[a,b] (const T& in, T& out) { out = (a * in + b); } );
}

template<typename T, int d>
void equalizeHistogram(const Mat &input, Mat &output)
{
}

#endif // IMAGEHELPER_IMPL_H
